<template>
  <div class="hamster-ppt-screen">
    <div 
      class="slide-list"
      @mousewheel="$event => mousewheelListener($event)"
      v-contextmenu="contextmenus"
    >
      <div 
        :class="[
          'slide-item', 
          `turning-mode-${slide.turningMode || 'slideY'}`,
          {
            'current': index === slideIndex,
            'before': index < slideIndex,
            'after': index > slideIndex,
            'hide': (index === slideIndex - 1 || index === slideIndex + 1) && slide.turningMode !== currentSlide.turningMode,
          }
        ]"
        v-for="(slide, index) in slides" 
        :key="slide.id"
      >
        <div 
          class="slide-content" 
          :style="{
            width: slideWidth + 'px',
            height: slideHeight + 'px',
          }"
        >
          <ScreenSlide 
            :slide="slide" 
            :scale="scale"
            :animationIndex="animationIndex"
          />
        </div>
      </div>
    </div>

    <SlideThumbnails 
      v-if="slideThumbnailModelVisible" 
      :turnSlideToIndex="turnSlideToIndex" 
      @close="slideThumbnailModelVisible = false"
    />

    <div class="tools">
      <IconLeftC class="tool-btn" @click="execPrev()" />
      <IconRightC class="tool-btn" @click="execNext()" />
      <IconSearch class="tool-btn" @click="slideThumbnailModelVisible = true" />
      <Popover trigger="click" v-model:visible="writingBoardToolVisible">
        <template #content>
          <WritingBoardTool @close="writingBoardToolVisible = false" />
        </template>
        <IconWrite class="tool-btn" />
      </Popover>
    </div>
  </div>
</template>

<script lang="ts">
import { computed, defineComponent, onMounted, onUnmounted, provide, ref } from 'vue'
import throttle from 'lodash/throttle'
import { MutationTypes, useStore } from '@/store'
import { Slide } from '@/types/slides'
import { VIEWPORT_ASPECT_RATIO, VIEWPORT_SIZE } from '@/configs/canvas'
import { KEYS } from '@/configs/hotkey'
import { ContextmenuItem } from '@/components/Contextmenu/types'
import { isFullscreen } from '@/utils/fullscreen'
import useScreening from '@/hooks/useScreening'

import ScreenSlide from './ScreenSlide.vue'
import SlideThumbnails from './SlideThumbnails.vue'
import WritingBoardTool from './WritingBoardTool.vue'

export default defineComponent({
  name: 'screen',
  components: {
    ScreenSlide,
    SlideThumbnails,
    WritingBoardTool,
  },
  setup() {
    const store = useStore()
    const slides = computed(() => store.state.slides)
    const slideIndex = computed(() => store.state.slideIndex)
    const currentSlide = computed<Slide>(() => store.getters.currentSlide)

    const slideWidth = ref(0)
    const slideHeight = ref(0)

    const scale = computed(() => slideWidth.value / VIEWPORT_SIZE)

    const slideThumbnailModelVisible = ref(false)

    const writingBoardToolVisible = ref(false)

    // 计算和更新幻灯片内容的尺寸（按比例自适应屏幕）
    const setSlideContentSize = () => {
      const winWidth = document.body.clientWidth
      const winHeight = document.body.clientHeight
      let width, height

      if (winHeight / winWidth === VIEWPORT_ASPECT_RATIO) {
        width = winWidth
        height = winHeight
      }
      else if (winHeight / winWidth > VIEWPORT_ASPECT_RATIO) {
        width = winWidth
        height = winWidth * VIEWPORT_ASPECT_RATIO
      }
      else {
        width = winHeight / VIEWPORT_ASPECT_RATIO
        height = winHeight
      }
      slideWidth.value = width
      slideHeight.value = height
    }

    // 窗口尺寸变化监听：窗口发生变化时更新幻灯片的大小
    // 如果退出了全屏，需要返回到编辑模式
    const { exitScreening } = useScreening()

    const windowResizeListener = () => {
      setSlideContentSize()
      if (!isFullscreen()) exitScreening()
    }

    onMounted(() => {
      window.addEventListener('resize', windowResizeListener)
    })
    onUnmounted(() => {
      window.removeEventListener('resize', windowResizeListener)
    })

    // 当前页的元素动画列表和当前执行到的位置
    const animations = computed(() => currentSlide.value.animations || [])
    const animationIndex = ref(0)

    // 执行元素的入场动画
    const runAnimation = () => {
      const prefix = 'animate__'
      const animation = animations.value[animationIndex.value]
      animationIndex.value += 1

      const elRef = document.querySelector(`#screen-element-${animation.elId} [class^=base-element-]`)
      if (elRef) {
        const animationName = `${prefix}${animation.type}`
        elRef.classList.add(`${prefix}animated`, animationName)

        const handleAnimationEnd = () => {
          elRef.classList.remove(`${prefix}animated`, animationName)
        }
        elRef.addEventListener('animationend', handleAnimationEnd, { once: true })
      }
    }

    // 向上/向下播放
    // 遇到元素动画时，优先执行动画播放，无动画则执行翻页
    // 向上播放遇到动画时，仅撤销到动画执行前的状态，不需要反向播放动画
    const execPrev = () => {
      if (animations.value.length && animationIndex.value > 0) {
        animationIndex.value -= 1
      }
      else if (slideIndex.value > 0) {
        store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value - 1)
        const lastIndex = animations.value ? animations.value.length : 0
        animationIndex.value = lastIndex
      }
    }
    const execNext = () => {
      if (animations.value.length && animationIndex.value < animations.value.length) {
        runAnimation()
      }
      else if (slideIndex.value < slides.value.length - 1) {
        store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value + 1)
        animationIndex.value = 0
      }
    }

    // 鼠标滚动翻页
    const mousewheelListener = throttle(function(e: WheelEvent) {
      if (e.deltaY < 0) execPrev()
      else if (e.deltaY > 0) execNext()
    }, 500, { leading: true, trailing: false })

    // 快捷键翻页
    const keydownListener = (e: KeyboardEvent) => {
      const key = e.key.toUpperCase()
      if (key === KEYS.UP || key === KEYS.LEFT) execPrev()
      else if (
        key === KEYS.DOWN || 
        key === KEYS.RIGHT ||
        key === KEYS.SPACE || 
        key === KEYS.ENTER
      ) execNext()
    }

    onMounted(() => {
      document.addEventListener('keydown', keydownListener)
    })
    onUnmounted(() => {
      document.removeEventListener('keydown', keydownListener)
    })

    // 切换到上一张/上一张幻灯片（无视元素的入场动画）
    const turnPrevSlide = () => {
      store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value - 1)
      animationIndex.value = 0
    }
    const turnNextSlide = () => {
      store.commit(MutationTypes.UPDATE_SLIDE_INDEX, slideIndex.value + 1)
      animationIndex.value = 0
    }

    // 切换幻灯片到指定的页面
    const turnSlideToIndex = (index: number) => {
      slideThumbnailModelVisible.value = false
      store.commit(MutationTypes.UPDATE_SLIDE_INDEX, index)
      animationIndex.value = 0
    }

    const contextmenus = (): ContextmenuItem[] => {
      return [
        {
          text: '上一张',
          subText: '↑、←',
          disable: slideIndex.value <= 0,
          handler: () => turnPrevSlide(),
        },
        {
          text: '下一张',
          subText: '↓、→',
          disable: slideIndex.value >= slides.value.length - 1,
          handler: () => turnNextSlide(),
        },
        { divider: true },
        {
          text: '查看所有幻灯片',
          handler: () => slideThumbnailModelVisible.value = true,
        },
        { divider: true },
        {
          text: '结束放映',
          subText: 'ESC',
          handler: exitScreening,
        },
      ]
    }
    
    provide('slideScale', scale)

    return {
      slides,
      slideIndex,
      currentSlide,
      slideWidth,
      slideHeight,
      scale,
      mousewheelListener,
      animationIndex,
      contextmenus,
      execPrev,
      execNext,
      slideThumbnailModelVisible,
      turnSlideToIndex,
      writingBoardToolVisible,
    }
  },
})
</script>

<style lang="scss" scoped>
.hamster-ppt-screen {
  width: 100%;
  height: 100%;
  position: relative;
  background-color: #111;
}
.slide-list {
  background: #1d1d1d;
  position: relative;
  width: 100%;
  height: 100%;
}
.slide-item {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;

  &.current {
    z-index: 2;
  }

  &.hide {
    opacity: 0;
  }

  &.turning-mode-no {
    &.before {
      transform: translateY(-100%);
    }
    &.after {
      transform: translateY(100%);
    }
  }
  &.turning-mode-fade {
    transition: opacity .75s;
    &.before {
      pointer-events: none;
      opacity: 0;
    }
    &.after {
      pointer-events: none;
      opacity: 0;
    }
  }
  &.turning-mode-slideX {
    transition: transform .35s;
    &.before {
      transform: translateX(-100%);
    }
    &.after {
      transform: translateX(100%);
    }
  }
  &.turning-mode-slideY {
    transition: transform .35s;
    &.before {
      transform: translateY(-100%);
    }
    &.after {
      transform: translateY(100%);
    }
  }
}
.slide-content {
  background-color: #fff;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  justify-content: center;
  align-items: center;
}

.tools {
  position: fixed;
  bottom: 8px;
  left: 8px;
  font-size: 25px;
  color: #666;
  z-index: 10;
  cursor: pointer;
}
.tool-btn {
  opacity: .35;

  &:hover {
    opacity: .7;
  }
  & + .tool-btn {
    margin-left: 8px;
  }
}
</style>